package br.com.spam.pipeline.core;

import java.util.ArrayList;
import java.util.List;
import java.util.Observable;

import javax.swing.JOptionPane;

import br.com.spam.pipeline.InstructionFactory;
import br.com.spam.pipeline.ReaderHelper;
import br.com.spam.pipeline.instruction.Instruction;
import br.com.spam.pipeline.instruction.InstructionPhase;
import br.com.spam.pipeline.phase.Phase;
import br.com.spam.pipeline.phase.PipelinePhase;

public class PipelineSimulator extends Observable
{
	private String[] sourceCode = null;
	private final List<Phase> phases = new ArrayList<Phase>();
	private boolean pause = true;
	private Memory memory;
	private Processor processor;

	public PipelineSimulator(String sourceCode)
	{
		this.phases.add(new Phase(PipelinePhase.IF));
		this.phases.add(new Phase(PipelinePhase.ID_RF));
		this.phases.add(new Phase(PipelinePhase.EX));
		this.phases.add(new Phase(PipelinePhase.MEM));
		this.phases.add(new Phase(PipelinePhase.WB));

		try
		{
			this.memory = new Memory();
		}
		catch (Exception ex)
		{
			JOptionPane.showMessageDialog(null, "Erro ao ler a memória: " + ex.getMessage());
			System.exit(1);
		}
		this.processor = new Processor(this);
		this.setSourceCode(sourceCode);
	}

	public List<Phase> getPhases()
	{
		return this.phases;
	}

	public void play()
	{
		if (this.sourceCode.length == 0)
			return;
		this.pause = false;
		new playCommand().start();
	}

	private void fetch()
	{
		try
		{
			if (this.sourceCode.length <= this.processor.getPC().getCorrectValue() / 4) { return; }
			if (this.phases.get(0).isAvailable())
			{
				Instruction nextInstruction = InstructionFactory.recognize(
						this.sourceCode[this.processor.getPC().getCorrectValue() / 4], this);
				nextInstruction.setIndex(this.processor.getPC().getCorrectValue() / 4 + 1);
				this.phases.get(0).setCurrentInstruction(nextInstruction);
				this.processor.getPC().write(this.processor.getPC().getCorrectValue() + 4,
						RegisterDataSource.JustResolvedJump);
			}
		}
		catch (IllegalStateException e)
		{
			return;
		}
	}

	private void writeBack()
	{
		Phase WB = this.phases.get(this.phases.size() - 1);
		if (WB.isFinished())
			this.processor.completeInstruction(WB.getCurrentInstruction());
	}

	private boolean nothingToDo()
	{
		if (this.processor.getPC().getPersistedValue() / 4 < this.sourceCode.length)
		{
			System.out.println("Code end not reached");
			return false;
		}

		for (Phase phase : this.phases)
		{
			if (!phase.isAvailable())
			{
				System.out.println("Phase not available: " + phase.getPhaseAsString());
				return false;
			}
		}

		return true;
	}

	public boolean next()
	{
		if (this.phases.get(this.phases.size() - 1).isFinished())
		{
			this.phases.get(this.phases.size() - 1).takeLastInstruction();
		}

		for (int i = this.phases.size() - 1; i > 0; i--)
		{
			Phase current = this.phases.get(i);
			Phase previous = this.phases.get(i - 1);
			if (current.isAvailable() && previous.isFinished())
			{
				current.setCurrentInstruction(previous.takeLastInstruction());
			}
		}

		if (this.nothingToDo())
		{
			super.setChanged();
			super.notifyObservers();
			return false;
		}

		this.fetch();

		for (Phase phase : this.phases)
		{
			phase.clockUp(InstructionPhase.FIRST_HALF);
		}
		for (Phase phase : this.phases)
		{
			phase.clockUp(InstructionPhase.SECOND_HALF);
		}

		this.writeBack();
		this.processor.clockUp();

		super.setChanged();
		super.notifyObservers();
		return true;
	}
	public void pause()
	{
		this.pause = true;
	}

	private void setSourceCode(String path)
	{
		String[] sourceCodeCandidate = ReaderHelper.getCodeAt(path, "Problemas ao abrir arquivo");
		if (sourceCodeCandidate != null)
		{
			this.sourceCode = sourceCodeCandidate;
		}
	}

	public MemoryPosition[] getRecentUsedMemory()
	{
		return this.memory.getRecentUsed();
	}

	public int getCurrentClock()
	{
		return this.processor.getCurrentClock();
	}

	public int getCompletedInstructions()
	{
		return this.processor.getCompletedInstructions();
	}

	public float getProdutivity()
	{
		return this.processor.getProdutivity();
	}

	public Processor getProcessor()
	{
		return this.processor;
	}

	public Memory getMemory()
	{
		return this.memory;
	}

	public class playCommand extends Thread
	{
		@Override
		public void run()
		{
			while (PipelineSimulator.this.next() && !PipelineSimulator.this.pause);

		}
	}

	public void resetPhasesDueToWrongPrediction()
	{
		this.phases.get(0).reset();
		this.phases.get(1).reset();
	}

}
